package task

import (
	"encoding/json"
	"fmt"
	"strings"
	"unicode/utf8"

	"code.byted.org/bytertc/whiteboard_server/biz/pkg/biz_error"
	"github.com/google/uuid"
	"github.com/pkg/errors"

	"code.byted.org/bytertc/whiteboard_server/biz/config"
	"code.byted.org/bytertc/whiteboard_server/biz/dal/ddl"
)

/*
server的task，能复用api数据结构则复用，未来如果内部逻辑修改导致有区别则修改
*/

type TaskUnique struct {
	AppId  string `json:"app_id"`
	TaskId string `json:"task_id"`
}

func (t *TaskUnique) GetAppId() string {
	return t.AppId
}

func (t *TaskUnique) GetTaskId() string {
	return t.TaskId
}

type Task struct {
	TaskUnique
	FileID          string          `json:"file_id"`
	UserId          string          `json:"user_id"`
	Status          TaskStatus      `json:"status"`
	TranscodeMode   TranscodeMode   `json:"transcode_mode"`
	TranscodeConfig TranscodeConfig `json:"transcode_config"`
	Priority        Priority        `json:"priority"`
	Resource        string          `json:"resource"`
	ResourceAttr    ResourceAttr    `json:"resource_attr"`
	StorageConfig   StorageConfig   `json:"storage_config"`
	PreSignDuration int64           `json:"pre_sign_duration"`
}

/*
type FileNameConfig struct {
	Prefix  []string `json:"prefix,omitempty"`
	Pattern string   `json:"pattern,omitempty"`

	prefix string
}

*/

func (t *Task) DynamicCheckAndUpdate() error {
	if t.AppId == "" {
		return biz_error.InvalidParam.Wrap(errors.Errorf("AppId can not be empty"))
	}
	if t.TaskId == "" {
		return biz_error.TranscodeError
	}
	if t.Resource == "" {
		return biz_error.InvalidParam.Wrap(errors.Errorf("Resource can not be empty"))
	}
	if t.ResourceAttr.FileName == "" {
		return biz_error.InvalidParam.Wrap(errors.Errorf("FileName can not be empty"))
	}
	if utf8.RuneCountInString(t.ResourceAttr.FileName) >= 200 {
		return biz_error.InvalidParam.Wrap(errors.Errorf("FileName too long"))
	}
	/*
		if path.Ext(t.ResourceAttr.FileName) != ".ppt" && path.Ext(t.ResourceAttr.FileName) != ".pptx" {
			return biz_error.InvalidParam.Wrap(errors.Errorf("FileName illegal"))
		}
	*/
	if t.ResourceAttr.Size <= 0 || t.ResourceAttr.Size > 300*1024*1024 {
		return biz_error.InvalidParam.Wrap(errors.Errorf("Size illegal"))
	}
	if t.TranscodeConfig.Thumbnail && t.TranscodeConfig.ThumbnailWidth <= 0 {
		t.TranscodeConfig.ThumbnailWidth = DefaultThumbnailWidth
	}

	if t.TranscodeConfig.Thumbnail && t.TranscodeConfig.ThumbnailHeight <= 0 {
		t.TranscodeConfig.ThumbnailHeight = DefaultThumbnailHeight
	}
	t.FileID = strings.Replace(uuid.NewString(), "-", "", -1)
	t.UserId = strings.Replace(uuid.NewString(), "-", "", -1)
	t.Status = Running
	return nil
}

func (t *Task) CheckAndUpdate() error {
	//check and set default value
	if t.AppId == "" {
		return biz_error.InvalidParam.Wrap(errors.Errorf("AppId can not be empty"))
	}
	if t.TaskId == "" {
		return biz_error.TranscodeError
	}
	if t.Resource == "" {
		return biz_error.InvalidParam.Wrap(errors.Errorf("Resource can not be empty"))
	}
	if !inArray(int(t.Priority), []int{int(RealTime), int(NonRealtime)}) {
		return biz_error.InvalidParam.Wrap(errors.Errorf("invalid Priority"))
	}
	if !inArray(int(t.TranscodeMode), []int{int(Statistic), int(Dynamic)}) {
		return biz_error.InvalidParam.Wrap(errors.Errorf("invalid TranscodeMode"))
	}
	if !inArray(int(t.TranscodeConfig.InputFormat), []int{int(InputFormatPPT), int(InputFormatPPTX), int(InputFormatDOC), int(InputFormatDOCX), int(InputFormatPDF)}) {
		return biz_error.InvalidParam.Wrap(errors.Errorf("invalid InputFormat"))
	}
	if !inArray(int(t.TranscodeConfig.OutputFormat), []int{int(OutputFormatPNG), int(OutputFormatJPEG)}) {
		return biz_error.InvalidParam.Wrap(errors.Errorf("invalid OutputFormat"))
	}
	if t.PreSignDuration < 0 || t.PreSignDuration > 604800 {
		return biz_error.InvalidParam.Wrap(errors.Errorf("invalid PreSignDuration"))
	}

	if t.TranscodeConfig.OutputWidth <= 0 {
		t.TranscodeConfig.OutputWidth = DefaultOutputWidth
	}

	if t.TranscodeConfig.OutputHeight <= 0 {
		t.TranscodeConfig.OutputHeight = DefaultOutputHeight
	}

	if t.TranscodeConfig.Thumbnail && t.TranscodeConfig.ThumbnailWidth <= 0 {
		t.TranscodeConfig.ThumbnailWidth = DefaultThumbnailWidth
	}

	if t.TranscodeConfig.Thumbnail && t.TranscodeConfig.ThumbnailHeight <= 0 {
		t.TranscodeConfig.ThumbnailHeight = DefaultThumbnailHeight
	}

	return nil
}

func (t *Task) ToPersist() *ddl.TranscodeTask {
	meta, _ := json.Marshal(t)
	return &ddl.TranscodeTask{
		AppId:         t.GetAppId(),
		TaskId:        t.GetTaskId(),
		UserId:        t.UserId,
		Status:        int(t.Status),
		TranscodeMode: int(t.TranscodeMode),
		Priority:      int(t.Priority),
		RetryTimes:    0,
		FileID:        t.FileID,
		FileSize:      t.ResourceAttr.Size,
		FileName:      t.ResourceAttr.FileName,
		Meta:          string(meta),
	}
}

func (t *Task) ToExecutorTask() (*ExecutorTask, error) {
	res := &ExecutorTask{
		TaskUnique:      t.TaskUnique,
		TranscodeMode:   t.TranscodeMode,
		TranscodeConfig: t.TranscodeConfig,
		Resource:        t.Resource,
		PreSignDuration: t.PreSignDuration,
		ExecutorStorageConfig: ExecutorStorageConfig{
			Type: t.StorageConfig.Type,
		},
	}

	switch t.StorageConfig.Type {
	case StoreInTos:
		c, err := genExecutorTosConfig(t.StorageConfig.TosConfig)
		if err != nil {
			return nil, err
		}
		res.ExecutorStorageConfig.ExecutorTosConfig = c
	case StoreInS3:
		c, err := genExecutorCustomConfig(t.StorageConfig.CustomConfig)
		if err != nil {
			return nil, err
		}
		res.ExecutorStorageConfig.ExecutorCustomConfig = c
	default:
		return nil, biz_error.InvalidParam.Wrap(errors.Errorf("invalid StorageType"))
	}

	return res, nil

}

func genExecutorTosConfig(tosConfig TosConfig) (ExecutorTosConfig, error) {
	res := ExecutorTosConfig{}
	if tosConfig.AccountId == "" {
		return res, biz_error.InvalidParam.Wrap(errors.Errorf("AccountId can not be empty"))
	}

	if tosConfig.Bucket == "" {
		return res, biz_error.InvalidParam.Wrap(errors.Errorf("Bucket can not be empty"))
	}

	regionID, ok := config.Settings().StorageRegionConfig.Tos.RegionIDs[tosConfig.Region]
	if !ok {
		return res, biz_error.InvalidParam.Wrap(errors.Errorf("invalid Region: %v", tosConfig.Region))
	}
	res = ExecutorTosConfig{
		AccountId: tosConfig.AccountId,
		Region:    regionID,
		EndPoint:  fmt.Sprintf(config.Settings().StorageRegionConfig.Tos.EndPointFormat, regionID),
		Bucket:    tosConfig.Bucket,
		Expire:    ExpireTime,
	}
	return res, nil
}

func genExecutorCustomConfig(customConfig CustomConfig) (ExecutorCustomConfig, error) {
	isVolcEngine := true //默认火山引擎客户，海外部署时需要修改
	res := ExecutorCustomConfig{}

	if customConfig.Bucket == "" {
		return res, biz_error.InvalidParam.Wrap(errors.Errorf("Bucket can not be empty"))
	}
	if customConfig.AccessKey == "" {
		return res, biz_error.InvalidParam.Wrap(errors.Errorf("AccessKey can not be empty"))
	}
	if customConfig.SecretKey == "" {
		return res, biz_error.InvalidParam.Wrap(errors.Errorf("SecretKey can not be empty"))
	}

	//判断不可用节点
	switch customConfig.Vendor {
	//amazon s3
	case 0:
		if isVolcEngine {
			if _, ok := VolcUnavailableAmazonS3Region[customConfig.Region]; ok {
				return res, biz_error.InvalidParam.Wrap(errors.Errorf("invalid Region: %v", customConfig.Region))
			}
		} else {
			if _, ok := BytePlusUnavailableAmazonS3Region[customConfig.Region]; ok {
				return res, biz_error.InvalidParam.Wrap(errors.Errorf("invalid Region: %v", customConfig.Region))
			}
		}
	//alicloud oss
	case 1:
		if isVolcEngine {
			if _, ok := VolcUnavailableAliCloudOSSRegion[customConfig.Region]; ok {
				return res, biz_error.InvalidParam.Wrap(errors.Errorf("invalid Region: %v", customConfig.Region))
			}
		} else {
			if _, ok := BytePlusUnavailableAliCloudOSSRegion[customConfig.Region]; ok {
				return res, biz_error.InvalidParam.Wrap(errors.Errorf("invalid Region: %v", customConfig.Region))
			}
		}
	}

	vendorID, ok := config.Settings().StorageRegionConfig.VendorIDs[customConfig.Vendor]
	if !ok {
		return res, biz_error.InvalidParam.Wrap(errors.Errorf("invalid Vendor"))
	}

	regionMap, ok := config.Settings().StorageRegionConfig.Custom[vendorID]
	if !ok {
		return res, biz_error.InvalidParam.Wrap(errors.Errorf("internal error, no corresponding regionMap, Vendor: %v", customConfig.Vendor))
	}
	regionID, ok := regionMap.RegionIDs[customConfig.Region]
	if !ok {
		return res, biz_error.InvalidParam.Wrap(errors.Errorf("invalid Region: %v", customConfig.Region))
	}

	endPoint := ""
	if regionMap.EndPointFormat != "" {
		endPoint = fmt.Sprintf(regionMap.EndPointFormat, regionID)
	}
	res = ExecutorCustomConfig{
		AccessKey: customConfig.AccessKey,
		SecretKey: customConfig.SecretKey,
		Region:    regionID,
		EndPoint:  endPoint,
		Bucket:    customConfig.Bucket,
		Expire:    ExpireTime,
	}
	return res, nil
}

func inArray(v int, dst []int) bool {
	for _, i := range dst {
		if v == i {
			return true
		}
	}
	return false
}
